package commons.io.in;

import commons.params_appli.ParamsAppli;
import commons.data.category.Category;
import commons.data.Attributes;
import commons.data.Model;
import commons.data.Model.TypeField;
import java.util.ArrayList;
import java.util.HashMap;
import commons.utils.primitive_geom.Rect_d;
import commons.utils.UtilsFile;
import commons.errors.ErrorsHandler;
import commons.logs.Logger;
import commons.utils.Utils;
import commons.utils.Utils.SeparateurDecimal;
import java.util.Collection;
import javax.swing.JOptionPane;


public abstract class File_in {

	public enum TypeCouche {PT, LIN};

	
	// le contenu du fichier a lire
	// permet de lire l'entete d'abord (model etc.) pour la DLG de parametrage,
	// puis le reste du fichier (données) en sortie de la DLG de paramétrage
	protected ArrayList<String> vLigne;
	
	protected int nbChiffrePrecision;

	TypeCouche typeCouche;
	public TypeCouche getTypeCouche() {return typeCouche;}

	String nomFichier;
	public String getNomFichier() {return nomFichier;}

	public int versionFormatFichier;
	public String nomsystemeProjection;
	

	String nomCouche;
	public String getNomCouche() {return nomCouche;}

	protected Model model_in; // sera créé lors de la lecture du fichier d'entrée
	public Model getModel() {return model_in;}
	public TypeField getTypeOf (String fieldName) {return model_in.getTypeOf(fieldName);}

	protected ArrayList<Attributes> vAttributes_in;
	public int getNbAttributes() {return vAttributes_in.size();}
	
	protected ArrayList<IObjetGeo_in> vObj;

	public ArrayList<? extends IObjetGeo_in> getObjects() {return vObj;}
	public void setVObj (ArrayList vObj) {
		this.vObj = vObj;
		createRectEmprise();
	}

	public int getNbObject() {return vObj.size();}
	public ArrayList<? extends IObjetGeo_in> getCopyOfVObj() {
		int size = 	vObj.size();
		ArrayList<IObjetGeo_in> vObj_copy = new ArrayList<IObjetGeo_in>(size);
		for (int i=0; i<size ; i++)
			vObj_copy.add(vObj.get(i).makeCopy());
		return vObj_copy;
	}


	// rectEmprise est calculé dans createFromFile(), après la lecture des données d'entrée
	// -> c'est l'emprise de toutes les données, meme si apres les données seront reduites par filtrage
	private Rect_d rectEmprise;
	public Rect_d getEmprise() {return rectEmprise;}


	public File_in(String nomFichier, ArrayList<String> vLigne) {
		vAttributes_in = new ArrayList<Attributes>();
		vObj = new ArrayList<IObjetGeo_in>();
		this.nomFichier = nomFichier;
		this.vLigne = vLigne;
		this.nomCouche = nomFichier.substring(0, nomFichier.length()-(ParamsAppli.getParam("common.EXTENSION_FICHIER_ECHANGE").length()+1)); // on enleve l'extension .txt
		this.nomsystemeProjection = "inconnu";

		this.nbChiffrePrecision = ParamsAppli.getParam_i("Autogene.PRECISION_X_Y_POUR_IO");
	}


	void clear() {
		model_in.clear();
		for (Attributes a : vAttributes_in)	a.clear();
		vAttributes_in.clear();
		vAttributes_in = null;

		for (IObjetGeo_in o : vObj) o.clear();
		vObj.clear();
		vObj = null;
		vLigne = null;
	}


	/**
	 * Filtre les objets sur la catégorie donnée.
	 * !! Les objets filtrés seront supprimés définitivement de vObj.
	 * @param cat la cétégorie
	 */
	public void filtreObjets_mainFilter (Category cat) {
		ArrayList<IObjetGeo_in> vObj_filtered = getObjectsForThisCategory(cat);
		vObj = vObj_filtered;
	}


	/**
	 * Renvoie un sous ensemble des données pour une catégorie donnée.
	 * Ne supprime aucune donnée et n'altère pas le vecteur vObj contenant les données
	 * @param cat la catégorie
	 */
	public ArrayList<IObjetGeo_in> getObjectsForThisCategory(Category cat) {
		ArrayList<IObjetGeo_in> vObj_filtered;
		vObj_filtered = new ArrayList<IObjetGeo_in>();

		for (IObjetGeo_in obj : vObj)
			if (obj.getAttributes_in().isOfCategory(cat))
				vObj_filtered.add(obj);
		return vObj_filtered;
	}


	// ne lit pas les données geogrpahiques
	public static File_in createFromFile_dontLoadData (String directory, String nomFichier) {
		String pathComplet = directory + "\\" + nomFichier;
	
		// Récupération du contenu du fichier sous forme d'un vecteur de String
		//  1 String par ligne du fichier
		ArrayList<String> vLigne = UtilsFile.readFile(pathComplet);
		if (ErrorsHandler.isThereErrors()) {
			ErrorsHandler.addError("Lecture du fichier impossible :\n -> " + pathComplet);
			return null;
		}

		//**********************************************************************
		// Lecture de la version de format du fichier
		int numVersion = 0;

		// on a besoin de lire au moins 2 lignes du fichier
		if (vLigne.size()<2) {
			ErrorsHandler.addError("Fichier incorrect (il contient moins de 2 lignes) :\n -> " + pathComplet);
			return null;
		}

		if (vLigne.get(0).equals("[VERSION FORMAT]")) {
			// Cette façon de décrire la version du format s'applique à partir de la v02.
			// Pour la v01, il n'y a pas du tout de balise [VERSION FORMAT]
			if (vLigne.get(1).toLowerCase().equals("v02"))
				numVersion = 2;
			else if(vLigne.get(1).toLowerCase().equals("v03"))
				numVersion = 3;
			else {
				numVersion = 0;
				ErrorsHandler.addError("La version du format indiquée dans le fichier est incorrecte : " + vLigne.get(1) + " -> ligne n°2 de :\n -> " + pathComplet);
			}
		} else {
			// Pour la v01, il n'y a pas du tout de balise [VERSION FORMAT]
			numVersion = 1;
		}

		switch (numVersion) {
			case 3  : return createFromFile_v03_dontLoadData(nomFichier, vLigne);
			default : ErrorsHandler.addError("Version du fichier d'échange non reconnue : " + numVersion); return null;
		}
	}

	// ne charge pas encore les données
	// le File_in conserve un vecteur des lignes du fichiers pour ensuite lire les données (en sortie de la DLG de paramétrage)
	private static File_in createFromFile_v03_dontLoadData (String nomFichier, ArrayList<String> vLigne) {
		File_in file_in = null;
		int nbLine = vLigne.size();
		String ligne;

		// Test minimal sur la taille du fichier, il doit y avoir au minimum 11 lignes
		//  (cas d'une unique objet avec un modele résuit à un unique attribut)
		if (nbLine<11) {
			ErrorsHandler.addError("Fichier incorrect (il ne contient pas suffisamment d'information) :\n -> " + nomFichier);
			return null;
		}


		//**********************************************************************
		// Lecture du type de la couche

		ligne = vLigne.get(2);
		if (!ligne.equals("[GEOM]")) {
			ErrorsHandler.addError("Balise [GEOM] manquante à la ligne n°3 : "  + nomFichier);
			return null;
		}

		ligne = vLigne.get(3);
		if (ligne.equals("LIN")) {
			file_in = new File_in_Segment(nomFichier, vLigne);
		} else if (ligne.equals("PT")) {
			file_in = new File_in_Point(nomFichier, vLigne);
		} else {
			ErrorsHandler.addError("Fichier incorrect (type de couche non pris en charge : " + ligne + ") :\n -> " + nomFichier);
			return null;
		}
		file_in.versionFormatFichier = 3;


		//**********************************************************************
		// Lecture du systeme de projection de la couche
		//  -> ne sert qu'à etre réécrit en sortie d'AutoGene
		ligne = vLigne.get(4);
		if (!ligne.equals("[PROJECTION]")) {
			ErrorsHandler.addError("Balise [PROJECTION] manquante à la ligne n°5 :\n -> "  + nomFichier);
			return null;
		}
		file_in.nomsystemeProjection = vLigne.get(5);

		//**********************************************************************
		// Lecture du modèle de la couche
		ligne = vLigne.get(6);
		if (!ligne.equals("[MODEL]")) {
			ErrorsHandler.addError("Balise [MODEL] manquante à la ligne n°7 :\n -> " + nomFichier);
			return null;
		}

		// On récupère les numéros de ligne debut et fin du modèle
		int numLigneModel_first, numLigneModel_last;
		numLigneModel_first = 7;
		numLigneModel_last = searchDataSection_v03(vLigne)-1;

		if (numLigneModel_last<0) {
			ErrorsHandler.addError("Fichier incorrect (ne contient pas de balise [DATA]) :\n -> " + nomFichier);
			return null;
		}
		if (numLigneModel_first > numLigneModel_last) {
			ErrorsHandler.addError("Fichier incorrect (ne contient aucune description de modèle après la balise [MODEL]) :\n -> " + nomFichier);
			return null;
		}

		// Création effective du Model
		file_in.model_in = new Model();
		file_in.model_in.initModel_v03(vLigne, numLigneModel_first, numLigneModel_last);
		// il y a peut-être eu une erreur ?
		if (ErrorsHandler.isThereErrors()) {
			ErrorsHandler.addError("Fichier incorrect (la partie [MODEL] contient des erreurs) :\n -> " + nomFichier);
			return null;
		}
		
		return file_in;
	}
	
	//**********************************************************************
	// Lecture des données
	public void readData() {
		int nbLine = vLigne.size();

		// On cherche la section des données
		int fstLineData;
		fstLineData = searchDataSection_v03(vLigne)+1;
		int nbLineData = nbLine - fstLineData;

		// Qques tests d'erreur
		if (nbLineData <= 0) {
			ErrorsHandler.addError("Fichier incorrect (aucune données dans la section [DATA]) :\n -> " + nomFichier);
			return;
		}
		if (nbLineData%2 != 0) {
			ErrorsHandler.addError("Fichier incorrect (il faut un nombre pair de ligne dans la section [DATA]) :\n -> " + nomFichier);
			return;
		}


		SeparateurDecimal separateurDecimalFichier = SeparateurDecimal.INCONNU;
		SeparateurDecimal separateurDecimalJava = Utils.getCurrentJavaSeparateurDecimal();

		// Lecture effective des données
		for (int i=fstLineData ; i<nbLine ; i+=2) {

			// *****************************************************************
			// Création de l'Attributes
			String lineAttributes = vLigne.get(i);
			String[] tabAttributes = Utils.mySplit(lineAttributes, '§'); //lineAttributes.split("§"); -> TROP LENT et fait pas exactement ce qu'on veut

			if (tabAttributes.length != model_in.getNumberOfField()) {
				ErrorsHandler.addError("Nombre d'attributs incohérent avec le nombre d'attribut du modèle -> ligne n°" + (i+1) + " :\n -> " + nomFichier);
				return;
			}

			Attributes attr_in = new Attributes(model_in, tabAttributes);
			vAttributes_in.add(attr_in);


			//******************************************************************
			// Creation de l'objet géographique
			String lineCoords = vLigne.get(i+1);

			// Sombre histoire du séparateur décimal
			if (separateurDecimalFichier==SeparateurDecimal.INCONNU)
				separateurDecimalFichier = guessSeparateurDecimal (lineCoords);
			// La v03 impose un "." comme séparateur décimal
			if (separateurDecimalFichier == SeparateurDecimal.VIRGULE) {
				ErrorsHandler.addError("Les séparateurs décimaux doivent êter des points :\n -> " + nomFichier);
				return;
			}

			// On ne convertit la ligne que si les parametre sont différent entre le fichier d'entrée et les paramètres Java locaux
			if (separateurDecimalFichier != SeparateurDecimal.INCONNU && separateurDecimalFichier != separateurDecimalJava) {
				if (separateurDecimalJava == SeparateurDecimal.POINT)
					lineCoords = lineCoords.replace(',', '.');
				else
					lineCoords = lineCoords.replace('.', ',');
			}

			String[] tabCoords = Utils.mySplit(lineCoords, '§');// lineCoords.split("§");

			if (tabCoords.length %2 != 0) {
				ErrorsHandler.addError("Il faut un nombre pair de coordonnées -> ligne n°" + (i+1) + " :\n -> " + nomFichier);
				return;
			}

			switch (typeCouche) {
				case LIN :
					// tabCoords contient une série de points -> création des Segment_in
					if (tabCoords.length < 4) {
						ErrorsHandler.addError("Pas assez de coordonnées pour décrive une ligne -> ligne n°" + (i+1) + " :\n -> " + nomFichier);
						return;
					}

					// la fonction suivante ajoute à vObj les segments lus
					((File_in_Segment)this).addSegments_from_tabCoord_v02(tabCoords, attr_in, i);
					break;
				case PT :
					// tabCoords ne doit contenir qu'une coordonnée (x,y)
					if (tabCoords.length != 2) {
						ErrorsHandler.addError("Il faut exactement 2 coordonnées pour une couche de type PT -> ligne n°" + (i+1) + " :\n -> " + nomFichier);
						return;
					}

					((File_in_Point)this).addPoint_from_tabCoord(tabCoords, attr_in);
					break;
			}

			// Peut être une erreur est-elle survenue à la lecture des coordonnées
			if (ErrorsHandler.isThereErrors())
				return;

		}
		createRectEmprise();
		
		// liberation memoire
		vLigne = null;
		
	}

	// renvoie le numero de ligne contenant la section [DATA]
	// -1 si pas trouvé
	private static int searchDataSection_v03(ArrayList<String> vLigne) {
		for (int i = 0; i < vLigne.size(); i++) {
			if (vLigne.get(i).equals("[DATA]"))
				return i;
		}
		return -1;
	}

	//**************************************************************************
	//**************************************************************************


	// On suppose bien entendu que la ligne testée (et également tout le fichier d'entrée) a un unique séparateur décimal ...
	private static SeparateurDecimal guessSeparateurDecimal(String lineCoords) {
		if (lineCoords.contains("."))
			return SeparateurDecimal.POINT;
		else if (lineCoords.contains(","))
			return SeparateurDecimal.VIRGULE;
		else
			return SeparateurDecimal.INCONNU;
	}


	protected void createRectEmprise() {
		double xMin, xMax, yMin, yMax;
		if (vObj==null || vObj.size()==0) return;
		// initialisation
		IObjetGeo_in obj = vObj.get(0);
		xMin = obj.getXMin();
		xMax = obj.getXMax();
		yMin = obj.getYMin();
		yMax = obj.getYMax();

		for (int i = 1; i < vObj.size(); i++) {
			obj = vObj.get(i);
			xMin = Math.min(xMin, obj.getXMin());
			xMax = Math.max(xMax, obj.getXMax());
			yMin = Math.min(yMin, obj.getYMin());
			yMax = Math.max(yMax, obj.getYMax());
		}
		rectEmprise = new Rect_d(xMin, yMin, xMax, yMax);
	}

	public void printStatsGlobales() {
		Logger.log_INFO1(" Résumé du fichier : " + nomFichier + "(format version " + versionFormatFichier + ")");
		//printStatsGlobalesSpecifiques();
	}
	public abstract void printStatsGlobalesSpecifiques();

	//**************************************************************************
	// AFFECTATION DES CATEGORIES
	//**************************************************************************

	/**
	 * Affecte à chaque Attributes du File_in une ou plusieurs catégories
	 * @param vCategory
	 */
	public void affecteCategories(ArrayList<Category> vCategory) {
		for (Attributes attr : vAttributes_in)
			attr.affecteCategories(vCategory);
	}

	/**
	 *
	 * @param vCategory_forOrder ne sert qu'à savoir l'ordre des catégories pour la rédaction de la stat
	 * il ne sert pas a affecter les category ! (ceci est fait par affecteCategories())
	 * @return
	 */
	public String getStatCategory(ArrayList<Category> vCategory_forOrder) {
		HashMap<String, Integer> mapCategory = new HashMap<String, Integer>();

		// Pour chaque attributes (c'est à dire, chaque élément du fichier d'entrée) ...
		for (Attributes attr : vAttributes_in) {
			// ... on parcours ses catégories et on incrémente un compteur stocké dans la map
			Collection<String> setCatName = attr.getCategoriesName();

			for (String catName : setCatName) {
				int nbAttrForThisCat;
				if (mapCategory.containsKey(catName))
					nbAttrForThisCat = mapCategory.get(catName) + 1;
				else
					nbAttrForThisCat = 1;
				mapCategory.put(catName, nbAttrForThisCat);
			}
		}

		String stat = "";
		for (Category cat : vCategory_forOrder) {
			int nbAttrForThisCat;
			if (mapCategory.get(cat.name) != null)
				nbAttrForThisCat = mapCategory.get(cat.name);
			else
				nbAttrForThisCat=0;
			stat += "  " + (cat.name + "                    ").substring(0, 20) + " : " + nbAttrForThisCat +  "\n";
		}
		

		return stat;
	}

}
